import type { UserPromptTemplateVariables } from "@shared/triplit/types";
import { PRESET_VARIABLES, PRESET_VARIABLES_MAP } from "./constant";

/**
 * Check if the value is a valid variable prompt.
 *
 * A variable-capable prompt is considered valid if it either:
 * - is empty/null (treated as permissive), or
 * - contains the special placeholder `{{#input#}}` somewhere in the string.
 *
 * This validation is used as a gate: only when valid do we apply variable replacement
 * to the user's input in the downstream rendering pipeline.
 *
 * @param value The prompt string to validate
 * @returns true if valid; otherwise false
 *
 * @example
 * isValidVariablePrompt(null)
 * // => true
 *
 * @example
 * isValidVariablePrompt("")
 * // => true
 *
 * @example
 * isValidVariablePrompt("Hello {{#input#}}")
 * // => true
 *
 * @example
 * isValidVariablePrompt("Hello {{#input#}")
 * // => false (missing closing tokens)
 *
 * @example
 * isValidVariablePrompt("No input here, only {{#date#}}")
 * // => false (must contain {{#input#}} to be considered valid)
 */
export function isValidVariablePrompt(value: string | null): boolean {
  if (!value) return true;
  const VARIABLE_PATTERN = /\{\{#input#\}\}/;
  return VARIABLE_PATTERN.test(value);
}

/**
 * Match all supported variables appearing in a string value.
 *
 * This function scans for placeholders of the form `{{#<key>#}}` and returns a list of
 * keys that are known/present in `PRESET_VARIABLES`. Unknown placeholders are ignored.
 *
 * Note: The order of returned variables follows their first appearance in the string.
 * Duplicates may be returned if the same variable appears multiple times in the string.
 *
 * @param value The string in which to find variable placeholders
 * @returns An array of matched variable keys filtered by `PRESET_VARIABLES`
 *
 * @example
 * matchedVariables('Hello {{#input#}}')
 * // => ['input']
 *
 * @example
 * matchedVariables('Hello {{#input#}} {{#date#}}')
 * // => ['input', 'date']
 *
 * @example
 * matchedVariables('Hello {{#input#} and {{#date#}}')
 * // => ['date'] (malformed input placeholder is ignored)
 *
 * @example
 * matchedVariables('Unknown {{#foo#}} and known {{#time#}}')
 * // => ['time'] (unknown key 'foo' is ignored)
 *
 * @example
 * matchedVariables('Repeat {{#time#}} then {{#time#}} again')
 * // => ['time', 'time'] (duplicates preserved by design)
 *
 * @example
 * matchedVariables(null)
 * // => []
 */
export function matchedVariables(value: string | null): string[] {
  if (!value) return [];
  const matched = value.matchAll(/\{\{#([^#}]+)#\}\}/g);
  return Array.from(matched)
    .map((m) => m[1])
    .filter((key) =>
      PRESET_VARIABLES.includes(key as UserPromptTemplateVariables),
    );
}

/**
 * Replace all occurrences of a specific variable placeholder with its value in a template string.
 *
 * This function searches for all instances of `{{#<key>#}}` in the template string and replaces
 * them with the provided value. It uses a global regular expression to ensure all occurrences
 * are replaced.
 *
 * @param template The template string containing variable placeholders
 * @param key The variable key to replace (without the `{{#` and `#}}` wrapper)
 * @param value The value to substitute for the variable placeholder
 * @returns An object containing the replaced string and the original value
 *
 * @example
 * replaceVar("Hello {{#name#}}, welcome!", "name", "John")
 * // => { replaced: "Hello John, welcome!", value: "John" }
 *
 * @example
 * replaceVar("{{#greeting#}} {{#name#}} {{#greeting#}}", "greeting", "Hi")
 * // => { replaced: "Hi {{#name#}} Hi", value: "Hi" }
 *
 * @example
 * replaceVar("No variables here", "name", "John")
 * // => { replaced: "No variables here", value: "John" }
 */
export const replaceVar = (template: string, key: string, value: string) => {
  const replaced = template.replace(
    new RegExp(`\\{\\{#${key}#\\}\\}`, "g"),
    value,
  );
  return { replaced, value };
};

/**
 * Convert a JSON representation of a preset prompt template to plain text format.
 *
 * This function parses a JSON state representing a structured prompt editor content
 * and converts it back to a plain text format where custom text remains as-is and
 * variable nodes are converted to their placeholder syntax `{{#<variable>#}}`.
 *
 * The JSON structure is expected to have:
 * - A root object with a children array (representing paragraphs)
 * - Each paragraph containing a children array (representing text nodes/variables)
 * - Text nodes with type "custom-text" and a text property
 * - Variable nodes with type "variable-value" and a variable property
 *
 * @param jsonState The JSON string representing the structured prompt content
 * @returns The converted plain text string with variable placeholders, or empty string if invalid
 *
 * @example
 * const json = '{"root":{"children":[{"children":[{"type":"custom-text","text":"Hello "},{"type":"variable-value","variable":"name"}]}]}}'
 * convertPresetJsonToText(json)
 * // => "Hello {{#name#}}"
 *
 * @example
 * convertPresetJsonToText('{"root":{"children":[]}}')
 * // => ""
 *
 * @example
 * convertPresetJsonToText('{"root":null}')
 * // => ""
 */
export function convertPresetJsonToText(jsonState: string): string {
  const parsed = JSON.parse(jsonState);
  const root = parsed.root;

  if (!root?.children || !Array.isArray(root.children)) {
    return "";
  }

  return (
    root.children
      // biome-ignore lint/suspicious/noExplicitAny: ignore
      .map((paragraph: any) => {
        if (!paragraph.children || !Array.isArray(paragraph.children)) {
          return "";
        }

        return (
          paragraph.children
            // biome-ignore lint/suspicious/noExplicitAny: ignore
            .map((node: any) => {
              if (node.type === "custom-text") {
                return node.text || "";
              }
              if (node.type === "variable-value") {
                return `{{#${node.variable}#}}`;
              }
              return "";
            })
            .join("")
        );
      })
      .join("\n")
  );
}

/**
 * Restore a render map back to its template format and apply the 'now' variable renderer.
 *
 * This function takes a render map (as a JSON string), reconstructs the template content
 * by replacing certain keys with their placeholder equivalents (specifically "now" becomes
 * "{{#now#}}"), and then applies the 'now' variable renderer to get the final rendered result.
 *
 * The function is used to reverse the rendering process and get back a processed template
 * where the 'now' variable has been properly rendered with its current value.
 *
 * @param renderMap The render map as a JSON string or object containing key-value pairs
 * @returns The restored and rendered content string, or null if the renderer is not available
 *
 * @example
 * const renderMap = '{"now": "current-time-value", "other": "text"}'
 * restoreRenderMap(renderMap)
 * // => "{{#now#}}text" (after applying the now renderer)
 *
 * @example
 * restoreRenderMap('{"text": "Hello World"}')
 * // => "Hello World" (after applying the now renderer, if available)
 */
// biome-ignore lint/suspicious/noExplicitAny: json type
export function restoreRenderMap(renderMap: any): string | null {
  const renderMapJsonObj = JSON.parse(renderMap);

  let newContent = "";
  Object.keys(renderMapJsonObj).forEach((key) => {
    newContent += key === "now" ? "{{#now#}}" : renderMapJsonObj[key];
  });

  const renderer = PRESET_VARIABLES_MAP.get("now")?.renderTemplate;

  return renderer?.(newContent).replaced ?? null;
}

/**
 * Restore a template string by applying render map transformations.
 *
 * This function takes a template string and a render map, then applies the render map
 * transformations to the template. For the special "now" key, it uses the dedicated
 * 'now' variable renderer from PRESET_VARIABLES_MAP. For other keys, it performs
 * simple string replacement of the key with its corresponding value from the render map.
 *
 * Unlike `restoreRenderMap`, this function works with an existing template string
 * rather than reconstructing one from the render map.
 *
 * @param template The template string to transform
 * @param renderMap The render map as a JSON string containing key-value pairs for replacements
 * @returns The transformed template string with all applicable replacements applied
 *
 * @example
 * const template = "Hello {{#now#}}, user123!"
 * const renderMap = '{"now": "current-time", "user123": "John"}'
 * restoreRenderMapByTemplate(template, renderMap)
 * // => "Hello [current-time-rendered], John!" (after applying the now renderer)
 *
 * @example
 * const template = "Welcome back, guest456!"
 * const renderMap = '{"guest456": "Alice"}'
 * restoreRenderMapByTemplate(template, renderMap)
 * // => "Welcome back, Alice!"
 */
export function restoreRenderMapByTemplate(
  template: string,
  // biome-ignore lint/suspicious/noExplicitAny: json type
  renderMap: any,
): string {
  if (!renderMap) return template;
  const renderMapJsonObj = JSON.parse(renderMap);

  Object.keys(renderMapJsonObj).forEach((key) => {
    if (key === "now") {
      const renderer = PRESET_VARIABLES_MAP.get("now")?.renderTemplate;
      template = renderer?.(template).replaced ?? "";
    } else {
      template = template.replace(key, renderMapJsonObj[key]);
    }
  });

  return template;
}
